/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* SCRMGR text editor & viewer */

#include <stdio.h>
#include <getopt.h>
#include <setjmp.h>
#include <unistd.h>
#include "smlib.h"
#include "smio.h"
#include "smedit.def"

CHAR *smedit_rcsid(void) {
    return("$Id: smedit.c,v 1.7 2008/02/19 13:15:50 bhepple Exp $");
}

#define NUM_SKEYS 3

static struct {
    CHAR	*rev_string;
    int		force_memory;
    int		force_file;
    int		view_only;
    CHAR 	last_filename[MAXPATHLEN + 1];
} global;

SM_FILE	screenset;

jmp_buf		NoMoreHeap;

static CHAR *usage_msg[] = {
"Edits data files according to SCRMGR screens.",
"",
"Backups of files are preserved in \"filename~\".",
"",
"-m         : force temporary storage into memory (heap)",
"-f         : force temporary storage onto disc",
"-v         : view only mode (default if invoked as smpg or smmore)",
"+linenum   : move to linenum",
"textfile   : text to be edited.",
NULL
};

#if !READ_DAT_AT_RUNTIME
static CHAR *smedit_dat[] = {
#include "smedit.dat"
NULL,
};
#endif

static void usage()
{
    CHAR **c;
    
    fprintf(stderr, "%s of %s.\n", global.rev_string, sm_program_name);
    fprintf(stderr, 
"Usage: %s [-m|f] [-v] [-c linenum] [textfile]\n", sm_program_name);
    
    c = usage_msg;
    while (*c) {
        fputs(*c, stderr);
        fputc('\n', stderr);
        c++;
    }
    exit(2);
}

void sm_error_exit ARG1(CHAR *, msg)
{
    sm_term();
    puts(msg);
    exit(1);
}

void re_fail ARG2(char *, msg, char, op)
{
    sm_message(msg);
}

/* Exported to the SCRMGR package */
f_ptr sm_get_field_trigger ARG1(SM_FIELD, field)
{
    return(NULL);
}

static int data_open ARG1(SM_SCREEN, screen)
{
    if (smdb_read_file(screen->filename, screen, sm_user_screens()->untabify))
        return(SM_ERROR);
    else
	return(0);
}

/* Note that this stores the filename on the screen data structure for
   future use.
*/
static int store_file ARG3(CHAR *, filename, SM_SCREEN, screen, int, emergency)
{
    if (smdb_write_file(filename, screen, emergency) == 0) {
	SM_ASSIGN(screen->filename, filename);
	return(0);
    } else
	return(SM_ERROR);
}

CALLBACK(store_file_as)
{
    FILE	*file;
    CHAR	*filename;

    /* Always entered after sm_prompt */
    if (command == SM_YES_C) {
	filename = sm_get_prompt_value();

	if ((file = fopen(filename, "r")) != NULL) {
	    CHAR	*buf = alloca(100);

	    fclose(file);
	    sprintf(buf, "\"%s\" already exists. Overwrite it?", filename);
	    if (sm_confirm(buf) != SM_YES_C)
		return(SM_QUIT_C);
	}
	store_file(filename, main_screen, 0);
	command = SM_POPCALLBACK;
    }

    return(command);
}

static void setup_store_file_as ARG1(SM_SOFTKEY, skey)
{
    CHAR	pwd[MAXPATHLEN + 1];

    GETCWD(pwd, MAXPATHLEN);
    sm_add_callback(store_file_as, NULL);
    sm_setup_prompt("Enter file to write (current directory is:)", 
		    pwd, 
		    global.last_filename,
		    skey);
}

CALLBACK(do_exit)
{
    /* We can't just return SM_EXIT_C in case we need to exit
       without saving files */
    if (command != SM_CANCEL_C)
	while (sm_destroy_callback())
	    ; /* To kill smedit_main_loop */
    return(SM_POPCALLBACK);
}

CALLBACK(load_file_done)
{
    if (command == SM_YES_C) {
	sm_message("Loading ...");
    	SM_ASSIGN(main_screen->filename, sm_get_prompt_value());
	strcpy(global.last_filename, main_screen->filename);
    	if (data_open(main_screen) == 0)
            sm_message("");
    }
    return(command);
}

int lambda ARG2(int, command, SM_SCREEN, screen)
{
    if (global.view_only) {
	switch (command) {
	case SM_JOIN_LINES_C:
	case SM_SPLIT_LINE_C:
	case SM_DEL_CHAR_C:
	case SM_DEL_BACK_C:
	case SM_CLEAR_FIELD_C:
	case SM_TRANSPOSE_CHARS_C:
	case SM_TOGGLE_CASE_C:
	case SM_DEL_WORD_C:
	case SM_DEL_WORD_BACK_C:
	case SM_TRANSPOSE_WORDS_C:
	case SM_CLEAR_SCREEN_C:
	case SM_INSERT_DATA_C:
	case SM_DELETE_DATA_C:
	case SM_YANK_DATA_C:
	case SM_CHANGE_TYPE_C:
	case SM_EDIT_MASTER_C:
	case SM_POPUP_C:
	case SM_LOAD_FILE_C:
	case SM_STORE_FILE_C:
	case SM_STORE_FILE_AS_C:
	case SM_CLEAR_FILE_C:
	case SM_SEARCH_REP_C:
	case SM_REPLACE_ALL_C:
	case SM_WHERE_C:
	case SM_SORT_C:
	    command = 0;
	    break;
	default:
	    if (((command >= ' ') && (command <= 0200)) ||
                ((command >= 0xa0) && (command <= 0xff)))
		command = 0;
	    break;
	}
    } else
	if ((command == SM_NEXT_LINE_C) &&
	    smio_insertp())
	    command = SM_SPLIT_LINE_C;
    
    switch (command) {
    case SM_NEXT_KEYS_C:
	command = 0;
	break;
	
    case SM_SPLIT_LINE_C:
	{
	    /* There's no really clean way to do this. */
	    int	row, col;
	    CHAR	temp;
	    CHAR	*line;

	    if (smdb_get_current(screen)) {
		line = alloca(strlen(smdb_get_current(screen)) + 1);
		strcpy(line, smdb_get_current(screen));
		smio_getyx(SM_BASE_WINDOW, &row, &col);
		if (col <= strlen(line)) {
		    temp = line[col];
		    line[col] = 0;
		    smdb_set_current(screen, line);
		    line[col] = temp;
		    line += col;
		    smdb_read_next(screen);
		    smdb_insert_rec(screen, line);
		    sm_push_command(SM_HOME_FIELD_C);
		    command = SM_REFRESH_C;
		} else
		    command = 0;
	    } else
		smdb_insert_rec(screen, "");
	}
	break;
	
    case SM_JOIN_LINES_C: 
	if (smdb_get_current(screen)) {
	    CHAR	*this_line = alloca(SM_MAXREC);
	    
	    strcpy(this_line, smdb_get_current(screen));
	    if (smdb_read_next(screen)) {
		strcpy(&this_line[strlen(this_line)], 
		       smdb_get_current(screen));
		smdb_delete_rec(screen);
		smdb_read_prev(screen);
		smdb_set_current(screen, this_line);
		command = SM_REFRESH_C;
	    } else
		command = 0;
        }
	break;

    case SM_INSERT_DATA_C:
	smdb_read_prev(screen);
	break;

    case SM_YANK_DATA_C:
	sm_push_command('Y');
	break;

    default:
	break;
    }
    return(command);
}

CALLBACK(smedit_main_loop)
{
    int			errors;
    
    
    switch(command) {
    case SM_LOAD_FILE_C:
	/* This only applies to the first file ... */
	if (!smdb_file_changed(main_screen) || 
	    (sm_confirm("Lose changes?") == SM_YES_C)) {
	    CHAR	pwd[MAXPATHLEN + 1];

	    smdb_delete_data(main_screen);
	    sm_add_callback(load_file_done, NULL);
	    GETCWD(pwd, MAXPATHLEN);
	    sm_setup_prompt("Enter filename to read (current directory is:)", 
			    pwd,
			    global.last_filename,
			    NULL);
	    return(0);
	}
	break;
	
    case SM_STORE_FILE_C:
	errors = store_file(main_screen->filename, main_screen, 0);
	if (errors == -1)
	    return(0);
	break;

    case SM_STORE_FILE_AS_C:
	setup_store_file_as(NULL);
	return(0);
	
    case SM_CLEAR_FILE_C:
	if (!smdb_file_changed(main_screen) || 
	    (sm_confirm("Clear data file?") == SM_YES_C)) {
	    smdb_delete_data(main_screen);
	    global.last_filename[0] = 0;
	    sm_message("File cleared");
	}
	break;
	
    case SM_EXIT_C:
	if (smdb_file_changed(main_screen)) {
	    sm_add_callback(do_exit, NULL);
	    setup_store_file_as(sm_skey_open(sm_builtin_screens(), 
					     "sm_save_exit_skey"));
	    return(0);
	}
    case SM_NO_SAVE_EXIT_C:
	return(command);
	break;

    default: /* Probably a return from one of the sub routines */
	break;
    }

    sm_setup_edit(SM_BASE_WINDOW, 
		  main_screen, 
		  SM_EXIT_C, 
		  lambda, 
		  smio_current_skey,
		  NULL);
    return(0);
}

int main ARG2(int, argc, CHAR **, argv)
{
    int			c;
    long		startline = 0;

    /* Temporary back door */
    if (setjmp(NoMoreHeap)) {
	alloca(0);
	sm_error_exit(sm_mem_error);
    }

    global.rev_string = "$Revision: 1.7 $";
    global.force_memory = FALSE;
    global.force_file = FALSE;
    global.view_only = FALSE;
    global.last_filename[0] = 0;

    sm_pre_init(argc, argv);

    if (strstr(sm_program_name, "pg") || strstr(sm_program_name, "more"))
	global.view_only = TRUE;

    /* Get rid of X options and enter X if appropriate */
    sm_init(1, argc, argv);

    while ((c = getopt(argc, argv, "mfv?")) != EOF)
        switch (c) {
	case 'm':
	    global.force_memory = TRUE;
	    global.force_file = FALSE;
	case 'f':
	    global.force_memory = FALSE;
	    global.force_file = TRUE;
	    break;
	case 'v':
	    global.view_only = TRUE;
	    break;
        default:  
	    /* Unfortunately, if we've entered X in sm_init, then any error 
	       messages disappear */
            sm_term();
	    usage();
	    break;
        }
    
    if ((optind < argc) && strlen(argv[optind]) && (*argv[optind] == '+')) {
	startline = AsciiToIndex(&argv[optind][1]) - 1;
	optind++;
    }

    if ((optind < argc) && (strlen(argv[optind]) != 0))
	/* Use first argument as text file */
	strcpy(global.last_filename, argv[optind++]);

#ifdef SM_DEBUG
    smio_flushinp();
#endif

    screenset = sm_user_screens();
#if READ_DAT_AT_RUNTIME
    {
	CHAR	buf[MAXPATHLEN + 1];

	buf[0] = 0;
#if NEEDS_PATH_FOR_PROGS
	strcpy(buf, sm_program_directory);
#endif
	strcat(buf, "smedit.dat");
	if (sm_fopen(screenset, buf) == SM_ERROR)
	    sm_error_exit("Error reading smedit.dat!");
    }
#else
    if (sm_dbopen(screenset, smedit_dat) == SM_ERROR)
	sm_error_exit("Error reading internal data block!");
#endif

#include "smedit.opn"

    /* Back door for when we run out of heap - horrible structure, but
       what can one do ?*/

    if (setjmp(NoMoreHeap)) {
	alloca(0);

	/* Prevent infinite loop */
	if (setjmp(NoMoreHeap)) {
	    alloca(0);
	    sm_error_exit(sm_mem_error);
	}

	sm_message(sm_mem_error);

	if (setjmp(NoMoreHeap)) {
	    /* Oh boy - really serious! */
	    alloca(0);
	    sm_error_exit("Ran out of heap - file(s) were not saved! Sorry!");
	}

	if (smdb_file_changed(main_screen))
	    store_file(main_screen->filename, main_screen, 1);
	alloca(0);

	if (setjmp(NoMoreHeap) == 0)
	    sm_term();
	exit(1);
    }

    if (*global.last_filename) {
	SM_ASSIGN(main_screen->filename, global.last_filename);
	data_open(main_screen);
	smdb_seek(main_screen, startline);
    } else
	smdb_read_file(NULL, main_screen, sm_user_screens()->untabify);

    /*************/
    /* Main loop */
    /*************/

    sm_set_skey(search_skey, "sm_ignore_case_c", 1);
    sm_message(global.rev_string);

    {
	int 	row, col;

	sm_enq_term(SM_BASE_WINDOW, &row, &col);
	sm_set_fld_len(sm_fld_open(main_screen, "line"), col - 1);
	if (global.view_only) {
	    sm_fld_prot(sm_fld_open(main_screen, "line"), 1);
	    sm_set_skey_label(main_skey,   1, 	"", ""); /* "sm_load_file_c" */
	    sm_set_skey_label(main_skey,   2,	"", ""); /* "sm_store_file_as_c" */
	    sm_set_skey_label(main_skey,   3, 	"", ""); /* "sm_clear_file_c" */
	    sm_set_skey_label(search_skey, 4, 	"", ""); /* "sm_search_rep_c" */
	    sm_set_skey_label(search_skey, 5, 	"", ""); /* "sm_replace_all_c" */
	    sm_set_skey_next_skey(main_skey, "search_skey");
	}
    }

    sm_add_callback(smedit_main_loop, NULL);	
    sm_setup_edit(SM_BASE_WINDOW, 
		  main_screen, 
		  SM_EXIT_C, 
		  lambda, 
		  search_skey,
		  NULL);
    sm_main_loop();
    sm_term();
    exit(0);
}

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
